﻿using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;
using System.Threading;
using Safebrowsing.Properties;

namespace Safebrowsing
{
    /// <summary>
    /// Data access object used to store and get chunks data
    /// </summary>
    public class SafebrowsingDao : IDisposable
    {
        #region Fields

        private readonly string m_ConnectionString;
        private readonly string m_DatabaseLocation;
        private readonly DbMaintenancePolicy m_DbMaintenancePolicy;
        private readonly Timer m_DbMaintenanceTimer;
        private DateTime m_LastActivityTime = DateTime.Now;
        private readonly object m_SyncRoot = new object();
        private readonly ReaderWriterLock m_Lock = new ReaderWriterLock();

        #endregion

        #region Constructors

        /// <summary>
        /// Creates an instance of the SafebrowsingDao object.
        /// It checks if database is already exists and if not or it has old version -- recreates it.
        /// </summary>
        /// <param name="databaseLocation"></param>
        /// <param name="dbMaintenancePolicy"></param>
        public SafebrowsingDao(string databaseLocation, DbMaintenancePolicy dbMaintenancePolicy)
        {
            Logger.Info("Initializing SafeBrowsingDao. Database location is: {0}", databaseLocation);

            m_DbMaintenancePolicy = dbMaintenancePolicy;
            m_DatabaseLocation = databaseLocation;
            m_ConnectionString = string.Format("Data Source={0}", databaseLocation);
            WriteTimeout = Constants.DB_WRITE_TIMEOUT;
            ReadTimeout = Constants.DB_READ_TIMEOUT;

            if (File.Exists(databaseLocation))
            {
                CheckDatabaseApiVersion();
            }
            else
            {
                CreateDatabase();
            }

            if (dbMaintenancePolicy != null)
            {
                m_DbMaintenanceTimer = new Timer(OnMaintenanceTimerCallback, null, dbMaintenancePolicy.DbMaintenanceTimerPeriod, dbMaintenancePolicy.DbMaintenanceTimerPeriod);
            }

            Logger.Info("SafebrowsingDao has been initialized successfully.");
        }

        /// <summary>
        /// Creates an instance of the SafebrowsingDao object without maintenance policy.
        /// This means there is not maintenance thread (which periodically vacuums or reindexes the database).
        /// </summary>
        /// <param name="databaseLocation"></param>
        public SafebrowsingDao(string databaseLocation) : this(databaseLocation, null)
        {
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Timeout for which writing thread is waiting to acquire Writer lock.
        /// When this timeout is expired -- writing method throws ApplicationException.
        /// Default value is 60000 ms.
        /// </summary>
        public int WriteTimeout { get; set; }

        /// <summary>
        /// Timeout for which reading thread is waiting to acquire Reader lock.
        /// If this timeout is expired -- waiting method just returns empty response.
        /// Default value is 25 ms.
        /// </summary>
        public int ReadTimeout { get; set; }

        #endregion

        #region Public DAO methods

        /// <summary>
        /// Saves api response.
        /// </summary>
        /// <param name="dataListResponse"></param>
        /// <param name="redirectResponses"></param>
        /// <exception cref="ApplicationException">Thrown when writer lock timed out.</exception>
        public void SaveApiResponse(DataListResponse dataListResponse, IEnumerable<RedirectResponse> redirectResponses)
        {
            Logger.Info("Saving api response for list {0}", dataListResponse.List.Name);

            m_Lock.AcquireWriterLock(WriteTimeout);

            try
            {
                using (var connection = new SQLiteConnection(m_ConnectionString))
                {
                    connection.Open();
                    using (SQLiteTransaction transaction = connection.BeginTransaction())
                    {
                        foreach (int addChunkNumber in dataListResponse.AddChunksToDelete)
                            DeleteAddChunk(dataListResponse.List, addChunkNumber, transaction);
                        foreach (int subChunkNumber in dataListResponse.SubChunksToDelete)
                            DeleteSubChunk(dataListResponse.List, subChunkNumber, transaction);
                        foreach (RedirectResponse redirectResponse in redirectResponses)
                        {
                            foreach (Chunk chunk in redirectResponse.AddChunks)
                                SaveAddChunk(dataListResponse.List, chunk, transaction);
                            foreach (Chunk chunk in redirectResponse.SubChunks)
                                SaveSubChunk(dataListResponse.List, chunk, transaction);
                        }
                        // Creating list if it does not exist
                        ExecuteNonQuery(Resources.Sql_InsertList, transaction, "list_id", dataListResponse.List.ListId, "list_name", dataListResponse.List.Name, "time_updated", new DateTime(2000, 1, 1));
                        ExecuteNonQuery(Resources.Sql_SaveList, transaction, "list_id", dataListResponse.List.ListId, "time_updated", DateTime.Now);
                        transaction.Commit();
                    }
                }
            }
            finally
            {
                m_Lock.ReleaseWriterLock();
            }

            Logger.Info("Api response for list {0} has been saved successfully", dataListResponse.List.Name);
        }

        /// <summary>
        /// Returns list of add chunks numbers
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        public List<int> GetAddChunksNumbers(SafebrowsingList list)
        {
            m_Lock.AcquireWriterLock(WriteTimeout);

            try
            {
                var addChunksNumbers = new List<int>();

                using (var connection = new SQLiteConnection(m_ConnectionString))
                {
                    connection.Open();
                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        command.CommandText = Resources.Sql_SelectAddChunksNumbers;
                        command.Parameters.Add(new SQLiteParameter("list_id", list.ListId));
                        using (SQLiteDataReader reader = command.ExecuteReader())
                        {
                            while (reader.Read())
                            {
                                addChunksNumbers.Add(reader.GetInt32(0));
                            }
                        }
                    }
                }

                LastActivityTime = DateTime.Now;
                return addChunksNumbers;
            }
            finally
            {
                m_Lock.ReleaseWriterLock();
            }
        }

        /// <summary>
        /// Returns list of sub chunks numbers
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        public List<int> GetSubChunksNumbers(SafebrowsingList list)
        {
            m_Lock.AcquireWriterLock(WriteTimeout);

            try
            {
                var addChunksNumbers = new List<int>();

                using (var connection = new SQLiteConnection(m_ConnectionString))
                {
                    connection.Open();
                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        command.CommandText = Resources.Sql_SelectSubChunksNumbers;
                        command.Parameters.Add(new SQLiteParameter("list_id", list.ListId));
                        using (SQLiteDataReader reader = command.ExecuteReader())
                        {
                            while (reader.Read())
                            {
                                addChunksNumbers.Add(reader.GetInt32(0));
                            }
                        }
                    }
                }

                LastActivityTime = DateTime.Now;
                return addChunksNumbers;
            }
            finally
            {
                m_Lock.ReleaseWriterLock();
            }
        }

        /// <summary>
        /// Searches for url hashes in the specified lists
        /// </summary>
        /// <param name="hostKey"></param>
        /// <param name="lists"></param>
        /// <returns></returns>
        public List<UrlHashEntry> FindUrlHashes(int hostKey, List<SafebrowsingList> lists)
        {
            Logger.Verbose("SafebrowsingDao.FindUrlHashes({0}, {1} lists)", hostKey, lists.Count);

            try
            {
                m_Lock.AcquireReaderLock(ReadTimeout);
            }
            catch
            {
                Logger.Verbose("Cannot acquire reader lock, returning empty response");
                // Cannot acquire reader lock -- returning empty response);
                return null;
            }

            try
            {
                var entries = new List<UrlHashEntry>();

                using (var connection = new SQLiteConnection(m_ConnectionString))
                {
                    connection.Open();
                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        command.CommandText = Resources.Sql_SelectUrlHashEntriesWithListId;
                        command.Parameters.Add(new SQLiteParameter("host_key", hostKey));
                        command.Prepare();
                        using (SQLiteDataReader reader = command.ExecuteReader())
                        {
                            while (reader.Read())
                            {
                                int chunkNumber = reader.GetInt32(0);
                                string prefix = reader.IsDBNull(1) ? null : reader.GetString(1);
                                string fullHash = reader.IsDBNull(2) ? null : reader.GetString(2);
                                int listId = reader.GetInt32(3);

                                SafebrowsingList list = SafebrowsingList.GetListById(listId);

                                if (list != null && lists.Contains(list)) 
                                    entries.Add(new UrlHashEntry(chunkNumber, hostKey, prefix, fullHash, list));
                            }
                        }
                    }

                    LastActivityTime = DateTime.Now;
                }

                Logger.Verbose("Found {0} entries for the host key {1}", entries.Count, hostKey);
                return entries;
            }
            finally
            {
                m_Lock.ReleaseReaderLock();
            }
        }

        /// <summary>
        /// Searches for url hashes in the specified list.
        /// </summary>
        /// <param name="list"></param>
        /// <param name="hostKey"></param>
        /// <returns></returns>
        public List<UrlHashEntry> FindUrlHashes(int hostKey, SafebrowsingList list)
        {
            Logger.Verbose("SafebrowsingDao.FindUrlHashes({0},{1})", list.Name, hostKey);

            try
            {
                m_Lock.AcquireReaderLock(ReadTimeout);
            }
            catch
            {
                Logger.Verbose("Cannot acquire reader lock, returning empty response");
                // Cannot acquire reader lock -- returning empty response);
                return null;
            }


            try
            {
                var entries = new List<UrlHashEntry>();

                using (var connection = new SQLiteConnection(m_ConnectionString))
                {
                    connection.Open();
                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        command.CommandText = Resources.Sql_SelectUrlHashEntries;
                        command.Parameters.Add(new SQLiteParameter("list_id", list.ListId));
                        command.Parameters.Add(new SQLiteParameter("host_key", hostKey));
                        command.Prepare();
                        using (SQLiteDataReader reader = command.ExecuteReader())
                        {
                            while (reader.Read())
                            {
                                int chunkNumber = reader.GetInt32(0);
                                string prefix = reader.IsDBNull(1) ? null : reader.GetString(1);
                                string fullHash = reader.IsDBNull(2) ? null : reader.GetString(2);
                                entries.Add(new UrlHashEntry(chunkNumber, hostKey, prefix, fullHash, list));
                            }
                        }
                    }

                    LastActivityTime = DateTime.Now;
                }

                Logger.Verbose("Found {0} entries in list {1} for host key {2}", entries.Count, list.Name, hostKey);
                return entries;
            }
            finally
            {
                m_Lock.ReleaseReaderLock();
            }
        }

        /// <summary>
        /// Saves full hash data
        /// </summary>
        /// <param name="list"></param>
        /// <param name="chunkNumber"></param>
        /// <param name="hostKey"></param>
        /// <param name="prefix"></param>
        /// <param name="fullHash"></param>
        public void SaveHash(SafebrowsingList list, int chunkNumber, int hostKey, string prefix, string fullHash)
        {
            Logger.Verbose("SafebrowsingDao.SaveHash({0},{1},{2},{3})", list.Name, hostKey, prefix, fullHash);

            try
            {
                m_Lock.AcquireReaderLock(ReadTimeout);
            }
            catch
            {
                Logger.Verbose("Cannot acquire reader lock, do not saving this full hash");
                // Cannot acquire reader lock -- returning empty response);
                return;
            }

            try
            {
                using (var connection = new SQLiteConnection(m_ConnectionString))
                {
                    connection.Open();

                    if (prefix == null)
                    {
                        ExecuteNonQuery(Resources.Sql_SaveHashPrefixNull, connection, "hash", fullHash, "list_id",
                                        list.ListId, "chunk_number", chunkNumber, "host_key", hostKey);
                    }
                    else
                    {
                        ExecuteNonQuery(Resources.Sql_SaveHash, connection, "hash", fullHash, "list_id", list.ListId,
                                        "chunk_number", chunkNumber, "host_key", hostKey, "prefix", prefix);
                    }
                }                
            }
            finally
            {
                m_Lock.ReleaseReaderLock();
            }
        }

        /// <summary>
        /// Gets last update time for the specified list
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        public DateTime GetTimeUpdated(SafebrowsingList list)
        {
            using (var connection = new SQLiteConnection(m_ConnectionString))
            {
                connection.Open();
                object timeUpdated = ExecuteScalar(Resources.Sql_SelectListTimeUpdated, connection, "list_id",
                                                   list.ListId);

                if (timeUpdated == DBNull.Value)
                {
                    ExecuteNonQuery(Resources.Sql_InsertList, connection, "list_id", list.ListId, "list_name", list.Name, "time_updated", new DateTime(2000, 1, 1));
                    return new DateTime(2000, 1, 1);
                }

                return (DateTime) timeUpdated;
            }
        }

        #endregion

        #region Database maintenance

        /// <summary>
        /// Callback for the maintenance thread. It runs periodically and checks if database is need to be reindexed or vacuumed
        /// </summary>
        /// <param name="state"></param>
        private void OnMaintenanceTimerCallback(object state)
        {
            if (m_DbMaintenancePolicy == null) return;

            try
            {
                DatabaseStatus status = Status;

                if (DateTime.Now.Subtract(LastActivityTime) > m_DbMaintenancePolicy.IdleTimeBeforeReindex &&
                    DateTime.Now.Subtract(status.LastReindexTime) > m_DbMaintenancePolicy.ReindexPeriod)
                {
                    Reindex();
                }

                if (DateTime.Now.Subtract(LastActivityTime) > m_DbMaintenancePolicy.IdleTimeBeforeVacuum &&
                    DateTime.Now.Subtract(status.LastVacuumTime) > m_DbMaintenancePolicy.VacuumPeriod)
                {
                    Vacuum();
                }
            }
            catch (Exception ex)
            {
                Logger.Warn("Error in database maintenance thread: {0}", ex);
            }
        }

        private DateTime LastActivityTime
        {
            get
            {
                lock (m_SyncRoot)
                {
                    return m_LastActivityTime;
                }
            }
            set
            {
                lock (m_SyncRoot)
                {
                    m_LastActivityTime = value;
                }
            }
        }

        /// <summary>
        /// Returns database status information
        /// </summary>
        private DatabaseStatus Status
        {
            get
            {
                using (var connection = new SQLiteConnection(m_ConnectionString))
                {
                    connection.Open();

                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        command.CommandText = Resources.Sql_SelectStatus;
                        using (SQLiteDataReader reader = command.ExecuteReader())
                        {
                            if (reader.Read())
                            {
                                string schemaVersion = reader.GetString(0);
                                DateTime lastVacuumTime = reader.GetDateTime(1);
                                DateTime lastReindexTime = reader.GetDateTime(2);

                                return new DatabaseStatus(schemaVersion, lastVacuumTime, lastReindexTime);
                            }

                            throw new ApplicationException("Database status table is empty");
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Resets all data in the database
        /// </summary>
        public void Reset()
        {
            Logger.Info("SafebrowsingDao.Reset");
            m_Lock.AcquireWriterLock(WriteTimeout);

            try
            {
                // Recreating database
                CreateDatabase();
            }
            finally
            {
                m_Lock.ReleaseWriterLock();
            }

            Logger.Info("Database has been reseted successfully");
        }

        /// <summary>
        /// Executes VACUUM command against our database
        /// </summary>
        private void Vacuum()
        {
            Logger.Info("SafebrowsingDao.Vacuum");
            m_Lock.AcquireWriterLock(WriteTimeout);

            try
            {
                using (var connection = new SQLiteConnection(m_ConnectionString))
                {
                    connection.Open();

                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        command.CommandText = "VACUUM";
                        command.ExecuteNonQuery();
                    }

                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        command.CommandText = Resources.Sql_UpdateStatusLastVacuumTime;
                        command.ExecuteNonQuery();
                    }
                }
            }
            finally
            {
                m_Lock.ReleaseWriterLock();
            }
            Logger.Info("Database has been vacuumed successfully");
        }

        /// <summary>
        /// Executes REINDEX command against our database
        /// </summary>
        private void Reindex()
        {
            Logger.Info("SafebrowsingDao.Reindex");
            m_Lock.AcquireWriterLock(WriteTimeout);

            try
            {
                using (var connection = new SQLiteConnection(m_ConnectionString))
                {
                    connection.Open();

                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        command.CommandText = "REINDEX";
                        command.ExecuteNonQuery();
                    }

                    using (SQLiteCommand command = connection.CreateCommand())
                    {
                        command.CommandText = Resources.Sql_UpdateStatusLastReindexTime;
                        command.ExecuteNonQuery();
                    }
                }
            }
            finally
            {
                m_Lock.ReleaseWriterLock();
            }

            Logger.Info("Database was reindexed successfully");
        }

        /// <summary>
        /// Checks database api version.
        /// </summary>
        private void CheckDatabaseApiVersion()
        {
            try
            {
                Logger.Info("Checking safebrowsing database version");

                using (var connection = new SQLiteConnection(m_ConnectionString))
                {
                    connection.Open();

                    DatabaseStatus status = Status;

                    Logger.Info("Database status is: {0}", status);
                    Logger.Info("Database schema version is {0}. Current schema version is {1}", status.SchemaVersion,
                                Constants.SCHEMA_VERSION);

                    if (Constants.SCHEMA_VERSION != status.SchemaVersion)
                    {
                        // Current database if for another safebrowsing api version. Recreating database.
                        CreateDatabase();
                    }
                }
            }
            catch
            {
                // Exception while checking api version -- recreating database
                CreateDatabase();
            }
        }

        /// <summary>
        /// Creates or recreates sqlite database
        /// </summary>
        private void CreateDatabase()
        {
            Logger.Info("Safebrowsing.CreateDatabase");

            // Deleting old db if exists);
            if (File.Exists(m_DatabaseLocation))
            {
                Logger.Info("Database file {0} exists. Deleting it.", m_DatabaseLocation);
                File.Delete(m_DatabaseLocation);
            }

            // Creating new db
            using (var connection = new SQLiteConnection(m_ConnectionString))
            {
                connection.Open();

                using (SQLiteCommand command = connection.CreateCommand())
                {
                    command.CommandText = Resources.createdb;
                    command.ExecuteNonQuery();
                    LastActivityTime = DateTime.Now;
                }
            }

            Logger.Info("Database {0} has been created", m_DatabaseLocation);
        }

        #endregion

        #region Helper methods

        private static Dictionary<string, object> GetParameters(params object[] parameters)
        {
            var paramsDictionary = new Dictionary<string, object>();
            for (int i = 0; i < parameters.Length; i += 2)
            {
                var parameterName = (string) parameters[i];
                object parameterValue = parameters[i + 1];
                paramsDictionary.Add(parameterName, parameterValue);
            }

            return paramsDictionary;
        }

        /// <summary>
        /// Executes nonquery command
        /// </summary>
        /// <param name="commandText"></param>
        /// <param name="transaction"></param>
        /// <param name="parameters"></param>
        private void ExecuteNonQuery(string commandText, SQLiteTransaction transaction,
                                            params object[] parameters)
        {
            Dictionary<string, object> commandParameters = GetParameters(parameters);

            using (SQLiteCommand command = transaction.Connection.CreateCommand())
            {
                command.CommandText = commandText;
                foreach (var pair in commandParameters)
                {
                    command.Parameters.Add(new SQLiteParameter(pair.Key, pair.Value ?? DBNull.Value));
                }
                command.ExecuteNonQuery();
                LastActivityTime = DateTime.Now;
            }
        }

        /// <summary>
        /// Executes non-query command
        /// </summary>
        /// <param name="commandText"></param>
        /// <param name="connection"></param>
        /// <param name="parameters"></param>
        private void ExecuteNonQuery(string commandText, SQLiteConnection connection, params object[] parameters)
        {
            using (SQLiteTransaction transaction = connection.BeginTransaction())
            {
                ExecuteNonQuery(commandText, transaction, parameters);
                transaction.Commit();
            }
        }

        /// <summary>
        /// Executes scalar command
        /// </summary>
        /// <param name="commandText"></param>
        /// <param name="transaction"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        private object ExecuteScalar(string commandText, SQLiteTransaction transaction,
                                            params object[] parameters)
        {
            Dictionary<string, object> commandParameters = GetParameters(parameters);

            using (SQLiteCommand command = transaction.Connection.CreateCommand())
            {
                try
                {
                    command.CommandText = commandText;
                    foreach (var pair in commandParameters)
                    {
                        command.Parameters.Add(new SQLiteParameter(pair.Key, pair.Value ?? DBNull.Value));
                    }
                    return command.ExecuteScalar();                    
                }
                finally
                {
                    LastActivityTime = DateTime.Now;
                }
            }
        }

        /// <summary>
        /// Executes scalar command
        /// </summary>
        /// <param name="commandText"></param>
        /// <param name="connection"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        private object ExecuteScalar(string commandText, SQLiteConnection connection, params object[] parameters)
        {
            using (SQLiteTransaction transaction = connection.BeginTransaction())
            {
                object value = ExecuteScalar(commandText, transaction, parameters);
                transaction.Commit();
                return value;
            }
        }

        #endregion

        #region Methods for saving and deleting chunks

        private void DeleteAddChunk(SafebrowsingList list, int chunkNumber, SQLiteTransaction transaction)
        {
            ExecuteNonQuery(Resources.Sql_DeleteAddChunk, transaction, "list_id", list.ListId, "chunk_number",
                            chunkNumber);
        }

        private void DeleteSubChunk(SafebrowsingList list, int chunkNumber, SQLiteTransaction transaction)
        {
            ExecuteNonQuery(Resources.Sql_DeleteSubChunk, transaction, "list_id", list.ListId, "chunk_number",
                            chunkNumber);
        }

        private void SaveAddChunk(SafebrowsingList list, Chunk chunk, SQLiteTransaction transaction)
        {
            ExecuteNonQuery(Resources.Sql_SaveAddChunk, transaction, "list_id", list.ListId, "chunk_number",
                            chunk.Number);
            SaveAddChunkData(list, chunk, transaction);
        }

        private void SaveAddChunkData(SafebrowsingList list, Chunk chunk, SQLiteTransaction transaction)
        {
            foreach (ChunkData chunkData in chunk.ChunkDataList)
            {
                if (chunkData.Prefixes != null && chunkData.Prefixes.Count > 0)
                {
                    foreach (string prefix in chunkData.Prefixes)
                    {
                        ExecuteNonQuery(Resources.Sql_SaveAddChunkDataPrefix, transaction, "list_id", list.ListId,
                                        "chunk_number", chunk.Number, "host_key", chunkData.HostKey, "prefix", prefix);
                    }
                }
                else
                {
                    ExecuteNonQuery(Resources.Sql_SaveAddChunkData, transaction, "list_id", list.ListId, "chunk_number",
                                    chunk.Number, "host_key", chunkData.HostKey);
                }
            }
        }

        private void SaveSubChunk(SafebrowsingList list, Chunk chunk, SQLiteTransaction transaction)
        {
            ExecuteNonQuery(Resources.Sql_SaveSubChunk, transaction, "list_id", list.ListId, "chunk_number",
                            chunk.Number);

            SaveSubChunkData(list, chunk, transaction);
        }

        private void SaveSubChunkData(SafebrowsingList list, Chunk chunk, SQLiteTransaction transaction)
        {
            foreach (ChunkData chunkData in chunk.ChunkDataList)
            {
                foreach (var pair in chunkData.SubDataPrefixes)
                {
                    if (pair.Value == null || pair.Value.Count == 0)
                    {
                        ExecuteNonQuery(Resources.Sql_SaveSubChunkDataHostKey, transaction, "list_id", list.ListId,
                                        "chunk_number", pair.Key, "host_key", chunkData.HostKey);
                    }
                    else
                    {
                        foreach (string prefix in pair.Value)
                        {
                            ExecuteNonQuery(Resources.Sql_SaveSubChunkDataPrefix, transaction, "list_id", list.ListId,
                                            "chunk_number", pair.Key, "host_key", chunkData.HostKey, "prefix", prefix);
                        }
                    }
                }
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            using (var waitForTimerToDispose = new AutoResetEvent(false))
            {
                if (m_DbMaintenanceTimer != null) m_DbMaintenanceTimer.Dispose(waitForTimerToDispose);
                waitForTimerToDispose.WaitOne();
            }
        }

        #endregion
    }
}